WebRTC 如何避免拥塞?
1. WebRTC 网络传输背景
1.1. WebRTC && 多数直播
大多数视频直播平台
子项 | 说明 |
---|---|
传输协议 | RTMP(Real Time MEssaging Protocol) |
优点 | 使用RTMP, 其基于TCP传输,跟flash等流媒体服务支持比较好,同时CDN支持良好 |
缺点 | 直播音视频数据量大,实时性要求比较高,TCP的重传机制和拥塞机制不适用于实时传输。传输控制依赖于TCP本身协议控制机制,网络成本较大不够灵活。 |
音视频对网络丢包有一定程度天然容忍性(常见优酷,爱奇艺都有缓冲)。使用UDP传输是不错到选择。
webRTC
子项 | 说明 |
---|---|
传输协议 | RTP: 针对流媒体传输的基础协议,定义Internet上传输音视频数据包。 RTCP:负责流媒体到传输质量保证,提供流量控制和拥塞控制等机制。 |
优点 | 包括ICE、STUN、TURN等信令交互、网络大洞(P2P),主流浏览器支持(chrome, firefox,opera, safari,edge等) |
2. WebRTC 处理
2.1. NACK
NACK( negative acknowledge character), 就是否定应答。与之对应的是TCP中的ACK( acknowledge character)。
在TCP中,接收端对于收到的包都要进行应答即发送ACK包,发送端通过接受ACK包,来确定发送的包已经被成功接收,以此来保证网络包的传输可靠。
RTP协议不保证传输的可靠性,所以接收端也就不会发送ACK包。如果这个包比较重要,可以给对端发送NACK包,来告诉这个包我没有收到,你如果‘还有’的话,就给我重新发送一遍(Retransmission)。
前面说到的丢包,都加了引号,这个“丢包”,有可能是真的丢了,也有可能是顺序乱了。我们知道网络包的到达,有时候不是一定严格按照包序到达的,我收到了很多较新的包了,某个旧的包还没有收到,我就认为它丢了,也没准儿一会儿它又到了。怎么判断“丢包”呢? 我们通过:
乱序的偏移来决定发起重传
根据预计到达时间已经超过一定时间了来发起重传 (一般是等待一个rtt+jitter的时长)。
更多丢包判断参见如下连接:WebRTC中丢包重传NACK实现分析
2.2. 前向纠错编码( FEC )
FEC(Forward Error Correction,前向纠错码),通过增加冗余来增强容错性到一种方法。如果没有FEC,接受端发现丢包时,需要通过发送NACK 来发起重传,但重传会影响延时性。
FEC则是在发送端增加冗余数据,接受端在数据丢失到情况下可以重建丢失的数据。
增加FEC冗余数据占据了有效带宽
上面两点涉及到取舍,冗余的数据与减少丢包导致的延时。不过,FEC到冗余度是可以根据网络状况动态调整。
2.3. 抖动缓冲( Jitterbuffer )
网络传输中总是存在着抖动,导致网络包不是均匀顺序到达,WebRTC利用一个缓冲区,进行等待与排序,这就是jitterbuffer。他能动态根据网络情况调整缓冲区的大小。
主流的实时音视频框架基本都会实现jitterbuffer功能,诸如WebRTC、doubango等。WebRTC的jitterbuffer按照功能分类的话,可以分为jitter和buffer。
jitterbuffer = jitter + buffer
buffer
buffer主要对丢包、乱序、延时到达等异常情况进行处理,还会和NACK、FEC(前向纠错码)、FIR(关键帧请求))等QOS(质量服务)相互配合。
buffer 主要分为如下几种type:
freeframes
incompleteframes
decodableframes
type | remark |
---|---|
freeframes | 新到的数据包会根据时间戳在incompleteframes 和decodableframes 中寻找,相同放置到相应队列,否则从freeframes弹出空frame |
incompleteframes | 至少有一个数据包,帧数据不完整 |
decodableframes | 可解码,此队列中有decodable和complete 状态。complete 状态帧可被解码线程取出解码,完成后将buffer 重新push 到freeframes 队列。 decodable会根据decode_error_mode 有不同的规则,QOS的不同策略会设置不同的decode_error_mode ,包含kNoErrors、kSelectiveErrors以及kWithErrors。decode_error_mode 就决定了解码线程从buffer中取出来的帧是否包含错误,即当前帧是否有丢包。 |
jitterbuffer与QOS策略联系紧密,比如,incompleteframes和decodable队列清除一些frame之后,需要FIR(关键帧请求),根据包序号检测到丢包之后要NACK(丢包重传)等。
jitter
网络延迟带来的抖动会让音视频的播放不平稳,如音频的颤音,视频的忽快忽慢。那么如何对抗jitter呢?增加延时。
jitter主要根据当前帧的大小和延时评估出jitterdelay,再结合decode delay、render delay以及音视频同步延时,得到render time,来控制平稳的渲染视频帧。
其中:
freeDelayMS: 两笔RTP1, RTP2 之间时间差
frameSizeBytes: 当前帧数据大小
incompleteFrame: 是否微完整帧
UpdateEstimate: 用卡尔曼滤波对帧间延迟进行滤波(具体算法这里不进行讨论)
在得到jitterdelay之后,通过jitterdelay+ decodedelay +renderdelay,再确保大于音视频同步的延时,加上当前系统时间得到rendertime,这样就可以控制播放时间。控制播放,也就间接控制了buffer的大小。
当然,仅仅通过动态的jitterbuffer 是无法完全解决网络拥塞的问题,根本上还是应该调整发送的码率。
2.4. 带宽自适应
带宽自适应是指在音视频的收发过程中,根据网络带宽的变化,自动的来调整发送码率,来适应带宽的变化。在带宽足够的情况下,增加帧率和码率,提高音视频的质量,带来更好的通信体验。在带宽不足的情况下,主动降低码率或者帧率,保证通信的流畅性和可用性,也是带来更好的通信体验。
带宽自适应的核心:如何准确的估计带宽。WebRTC在实现带宽自适应时采用了Google提出一个称为REMB(Receiver Estimated Max Bitrate,最大接收带宽估计)的带宽估计算法。
大致算法是:
接受端维护状态机(根据丢包率或延时情况)
根据丢包率或延时情况修改remb 值
将remb 值通过RTCP 发送给发送端
发送端根据remb 值调整码率
补充:码流 / 码率 / 比特率 / 帧速率 / 分辨率 / 高清的区别